﻿/*

Copyright 2010 OK HOSTING S.C.
info@okhosting.com
okhosting.com

La librería OKHOSTING.FacturaGratis es software gratuito: puede redistribuirlo 
y/o modificarlo de acuerdo a los términos expuestos en la licencia pública general 
GNU, publicada por la fundación de software libre (Free Software Foundation),
considerando la versión 3 de la licencia o cualquier otra superior.

OKHOSTING.FacturaGratis se distribuye con la esperanza de que le será de utilidad,
no obstante, no se ofrece garantía de ningun tipo, ni en el aspecto comercial o
de que la solución resulte adecuada a su negocio particular. Por favor revise la
licencia publica general GNU para mas información.

Usted debe haber recibido una copia de la licencia publica general GNU junto con 
la librería OKHOSTING.FacturaGratis, pero en caso contrario lo invitamos a que 
revise http://www.gnu.org/licenses/

******************************************************************************
OKHOSTING.FacturaGratis library is free software: you can redistribute 
it and/or modify it under the terms of the GNU General Public License as 
published by the Free Software Foundation, either version 3 of the License, 
or any later version.

OKHOSTING.FacturaGratis is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
more details.

You should have received a copy of the GNU General Public License along with 
OKHOSTING.FacturaGratis Library.  If not, see <http://www.gnu.org/licenses/>.

*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Schema;
using System.IO;
using System.Xml.Serialization;

namespace OKHOSTING.FacturaGratis
{
	/// <summary>
	/// Crea facturas digitales para México siguiendo el estandar del SAT vigente a Junio del 2010
	/// </summary>
	public class Factura
	{

		/// <summary>
		/// RFC que debe usarse cuando se generen facturas para el público en general, incluyendo extrangeros y mexicanos sin RFC
		/// </summary>
		public const string RFCPublicoEnGeneral = "XAXX010101000";

		#region Propiedades públicas

		/// <summary>
		/// Ruta de la carpeta donde se guardan las facturas
		/// </summary>
		public readonly string CarpetaFacturas = AppDomain.CurrentDomain.BaseDirectory + @"Custom\FacturacionElectronica\Facturas\";
		
		/// <summary>
		/// Ruta de la carpeta donde se encuentran los recuros
		/// </summary>
		public readonly string CarpetaRecursos = AppDomain.CurrentDomain.BaseDirectory + @"Custom\FacturacionElectronica\Recursos\";

		/// <summary>
		/// Ruta de la carpeta donde se guardan los archivos temporales
		/// </summary>
		public readonly string CarpetaTemporal = AppDomain.CurrentDomain.BaseDirectory + @"Custom\FacturacionElectronica\Temporal\";
		
		/// <summary>
		/// Version del XML, debe ser 2.0
		/// </summary>
		public string Version;

		/// <summary>
		/// Serie de la factura que se va a crear
		/// </summary>
		/// <example>A, B, C o cualquier cadena</example>
		public string Serie;

		/// <summary>
		/// El número de la factura
		/// </summary>
		/// <example>100</example>
		public int Folio;

		/// <summary>
		/// Fecha y hora en que se crea la factura, debe contener hora, minutos y segundos
		/// </summary>
		/// <example>1/1/2008 10:30:00 A.M.</example>
		public DateTime Fecha;

		/// <summary>
		/// El sello digital, se genera automaticamente con la función CrearSello usando openssl.exe
		/// </summary>
		public string Sello;

		/// <summary>
		/// Número de aprocación de los folios. Este dato lo otorga la SHCP al solicitar los folios.
		/// </summary>
		/// <example>2000</example>
		public string NoAprobacion;

		/// <summary>
		/// Año de aprobación de los folios. Este dato lo otorga la SHCP al solicitar los folios.
		/// </summary>
		/// <example>2008</example>
		public string AnoAprobacion;

		/// <summary>
		/// La forma de pago para la factura
		/// </summary>
		/// <remarks>Obligatorio</remarks>
		/// <example>Pago en una sola excibición</example>
		public string FormaDePago;

		/// <summary>
		/// El número del certificado con el que se autentifica la factura
		/// </summary>
		/// <example>00001000000001655555</example>
		public string NoCertificado;

		/// <summary>
		/// El certificado con el que se autentifica la facturas
		/// </summary>
		/// <remarks>Este certificado se obtiene convirtiendo el certificado de la FIEL a formato PEM usando openssl</remarks>
		/// <example>
		/// MIICXgIBAAKBgQDKRVP186zuDWHP9BDOGPAOfJaqBlKKaNN6FV0mkO6iyG7TlpWr
		/// O3IBRBX4lw5k5MEDBwLxFmRQJ68ZHkaPDdBfGi3SO6VA+rkt50tlH5bLcSycWDAk
		/// CJ7U72TWDypx69TcafQwpr2vrfXPRmEz/kie5vF0H3tVkVxn5WQ6YUAMeQIDAQAB
		/// AoGAH3sazjTWvVYn2w3Jb8pB0n9hk6TYQ+J8x4t7q/zypzM6zIOrV7Mw0zGdmcso
		/// 2lsgDzCQLKWnhzIl9mrX4Hvt6hhDm/eAWLQ7JDYx2sQ2OO/HmizYKU4QF8pInxCu
		/// vlFxt/a4t9+KEf2smgbJUR6wgvhq76UzJigbwy1S27DRiAECQQD2Kr2hMGa1DIpI
		/// x+nufzhgXcxBoR3IpGkKhUx6oEKswMSiInhuyNp/MvYJgJbMwxT77mXBNoobH6a6
		/// rMD3+YBZAkEA0lm2judLZk5CZ/Lkc4bha/NOeHuYQ8IOEW/eE+RcnnM6tLpDWwt7
		/// o6cl2jVY7TbKJUq8CxLqgG3dP9kuOcJpIQJBAKRKVDLu1a1BiE0Yt0TIPXz7POYU
		/// PId7SuuNmURCDx2yrckzzkLJ5CF+hnxDCOHx1OBq9BhmaPe/QQxXXZZiO0kCQQC0
		/// PnWFHEJqprKWWfZR3Aj7NGBQMy/1F6pwXJhCGVMX3ws148llkYBfahGwWjgaA/HR
		/// ZKmfH5VbeUi1tka67ZChAkEAzaiCnVtXMIuXsIuMLXgn9GKRvjKfU7y6M1jGDlha
		/// KkRe3/2W5yODqZT1CgNJK8GuHgUWXeqKeYQEEGvQUE5cVg==
		/// </example>
		public string Certificado;

		/// <summary>
		/// Condiciones para el pago
		/// </summary>
		/// <remarks>Opcional</remarks>
		public string CondicionesDePago;

		/// <summary>
		/// Subtotal de la factura sin incluir impuestos, descuentos niretenciones. Equivale a la suma del importe de todos los conceptos
		/// </summary>
		public decimal Subtotal;

		/// <summary>
		/// Descuento que se aplica a la factura
		/// </summary>
		/// <remarks>Opcional</remarks>
		public decimal Descuento;

		/// <summary>
		/// Motivo por el cual se aplica un descuento
		/// </summary>
		/// <remarks>Opcional</remarks>
		public string MotivoDescuento;

		/// <summary>
		/// Total a pagar. Equivale al Subtotal - Descuento + Traslados - Retenciones
		/// </summary>
		public decimal Total;

		/// <summary>
		/// Método de pago
		/// </summary>
		/// <example>Deposito en cuenta, Efectivo, Cheque, etc.</example>
		/// <remarks>Opcional</remarks>
		public string MetodoDePago;

		/// <summary>
		/// Tipo de comprobante
		/// </summary>
		public TipoDeComprobante TipoDeComprobante;

		/// <summary>
		/// Namespace del archivo XML
		/// </summary>
		/// <example>http://www.sat.gob.mx/cfd/2</example>
		public string Xmlns;

		/// <summary>
		/// Esquema del XML
		/// </summary>
		/// <example>http://www.w3.org/2001/XMLSchema-instance</example>
		public string Xmlns_Xsi;

		/// <summary>
		/// Ubicación del esquema del XML
		/// </summary>
		/// <example>http://www.sat.gob.mx/cfd/2 http://www.sat.gob.mx/sitio_internet/cfd/2/cfdv2.xsd</example>
		public string Xsi_SchemaLocation;

		/// <summary>
		/// Empresa que emite la factura
		/// </summary>
		public Empresa Emisor;

		/// <summary>
		/// Empresa a quien va dirigida la factura
		/// </summary>
		public Empresa Receptor;

		/// <summary>
		/// Domicilio fiscal del Emisor
		/// </summary>
		public Domicilio DomicilioFiscalEmisor;

		/// <summary>
		/// Domicilio fiscal del Receptor
		/// </summary>
		public Domicilio DomicilioFiscalReceptor;

		/// <summary>
		/// Lista de conceptos, productos o servicios que se están facturando
		/// </summary>
		public List<Concepto> Conceptos = new List<Concepto>();

		/// <summary>
		/// Lista de impuestos retenidos
		/// </summary>
		public List<Retencion> Retenciones = new List<Retencion>();

		/// <summary>
		/// Lista de impuestos trasladados
		/// </summary>
		public List<Traslado> Traslados = new List<Traslado>();

		/// <summary>
		/// Ruta donde se guardó el archivo XML
		/// </summary>
		/// <remarks>Se establece automáticamente con la funcion Guardar()</remarks>
		private string rutaXML;

		/// <summary>
		/// Ruta donde se guardó el archivo XML
		/// </summary>
		/// <remarks>Se establece automáticamente con la funcion Guardar()</remarks>
		public string RutaXML
		{ get { return this.rutaXML; } }
		
		/// <summary>
		/// Ruta donde se guardó el archivo HTML para impresión
		/// </summary>
		/// <remarks>Se establece automáticamente con la funcion GuardarHTML()</remarks>
		private string rutaHTML;

		/// <summary>
		/// Ruta donde se guardó el archivo HTML para impresión
		/// </summary>
		/// <remarks>Se establece automáticamente con la funcion GuardarHTML()</remarks>
		public string RutaHTML
		{ get { return this.rutaHTML; } }
		
		/// <summary>
		/// Devuelve el número de factura incluyendo la serie
		/// </summary>
		/// <example>A100</example>
		public string NoFactura
		{
			get
			{
				return Serie + Folio;
			}
		}

		#endregion

		#region Crear XML

		/// <summary>
		/// Crea la factura en formato XML
		/// </summary>
		/// <returns>Instancia del XmlDocument conteniendo toda la información de la factura</returns>
		protected XmlDocument CrearXML()
		{
			//Inicializar
			XmlDocument xmlDoc = new XmlDocument();
			xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null));

			//Comprobante
			XmlElement xml_comprobante = xmlDoc.CreateElement("Comprobante");
			
			//Esquema
			SetXML(xml_comprobante, "xmlns", Xmlns);
			SetXML(xml_comprobante, "xmlns:xsi", Xmlns_Xsi);
			XmlAttribute schemaLocation = xmlDoc.CreateAttribute("xsi", "schemaLocation", Xmlns_Xsi);
			schemaLocation.Value = Xsi_SchemaLocation;
			xml_comprobante.SetAttributeNode(schemaLocation);

			SetXML(xml_comprobante, "version", Version);
			SetXML(xml_comprobante, "serie", Serie, true);
			SetXML(xml_comprobante, "folio", Folio);
			SetXML(xml_comprobante, "fecha", Fecha);
			SetXML(xml_comprobante, "sello", Sello, true); //ponemos el sello opcional, para poder generar la factura sin sello
			SetXML(xml_comprobante, "noAprobacion", NoAprobacion);
			SetXML(xml_comprobante, "anoAprobacion", AnoAprobacion);
			SetXML(xml_comprobante, "formaDePago", FormaDePago);
			SetXML(xml_comprobante, "noCertificado", NoCertificado);
			SetXML(xml_comprobante, "certificado", Certificado, true);
			SetXML(xml_comprobante, "condicionesDePago", CondicionesDePago, true);
			SetXML(xml_comprobante, "subTotal", Subtotal);
			if(Descuento > 0) SetXML(xml_comprobante, "descuento", Descuento, true);
			SetXML(xml_comprobante, "motivoDescuento", MotivoDescuento, true);
			SetXML(xml_comprobante, "total", Total);
			SetXML(xml_comprobante, "metodoDePago", MetodoDePago, true);
			SetXML(xml_comprobante, "tipoDeComprobante", TipoDeComprobante);
			
			xmlDoc.AppendChild(xml_comprobante);

			//Emisor
			XmlElement xml_emisor = xmlDoc.CreateElement("Emisor");
			SetXML(xml_emisor, "rfc", Emisor.RFC);
			SetXML(xml_emisor, "nombre", Emisor.Nombre);
			xml_comprobante.AppendChild(xml_emisor);

			//Domicilio del emisor
			XmlElement xml_domicilioFiscalEmisor = xmlDoc.CreateElement("DomicilioFiscal");
			SetXML(xml_domicilioFiscalEmisor, "calle", DomicilioFiscalEmisor.Calle);
			SetXML(xml_domicilioFiscalEmisor, "noExterior", DomicilioFiscalEmisor.NoExterior, true);
			SetXML(xml_domicilioFiscalEmisor, "noInterior", DomicilioFiscalEmisor.NoInterior, true);
			SetXML(xml_domicilioFiscalEmisor, "colonia", DomicilioFiscalEmisor.Colonia, true);
			SetXML(xml_domicilioFiscalEmisor, "localidad", DomicilioFiscalEmisor.Localidad, true);
			SetXML(xml_domicilioFiscalEmisor, "referencia", DomicilioFiscalEmisor.Referencia, true);
			SetXML(xml_domicilioFiscalEmisor, "municipio", DomicilioFiscalEmisor.Municipio);
			SetXML(xml_domicilioFiscalEmisor, "estado", DomicilioFiscalEmisor.Estado);
			SetXML(xml_domicilioFiscalEmisor, "pais", DomicilioFiscalEmisor.Pais);
			SetXML(xml_domicilioFiscalEmisor, "codigoPostal", DomicilioFiscalEmisor.CodigoPostal);
			xml_emisor.AppendChild(xml_domicilioFiscalEmisor);

			//Receptor
			XmlElement xml_receptor = xmlDoc.CreateElement("Receptor");
			SetXML(xml_receptor, "rfc", Receptor.RFC);
			SetXML(xml_receptor, "nombre", Receptor.Nombre, true);
			xml_comprobante.AppendChild(xml_receptor);

			//Domicilio del receptor
			XmlElement xml_domicilioFiscalReceptor = xmlDoc.CreateElement("Domicilio");
			SetXML(xml_domicilioFiscalReceptor, "calle", DomicilioFiscalReceptor.Calle, true);
			SetXML(xml_domicilioFiscalReceptor, "noExterior", DomicilioFiscalReceptor.NoExterior, true);
			SetXML(xml_domicilioFiscalReceptor, "noInterior", DomicilioFiscalReceptor.NoInterior, true);
			SetXML(xml_domicilioFiscalReceptor, "colonia", DomicilioFiscalReceptor.Colonia, true);
			SetXML(xml_domicilioFiscalReceptor, "localidad", DomicilioFiscalReceptor.Localidad, true);
			SetXML(xml_domicilioFiscalReceptor, "referencia", DomicilioFiscalReceptor.Referencia, true);
			SetXML(xml_domicilioFiscalReceptor, "municipio", DomicilioFiscalReceptor.Municipio, true);
			SetXML(xml_domicilioFiscalReceptor, "estado", DomicilioFiscalReceptor.Estado, true);
			SetXML(xml_domicilioFiscalReceptor, "pais", DomicilioFiscalReceptor.Pais);
			SetXML(xml_domicilioFiscalReceptor, "codigoPostal", DomicilioFiscalReceptor.CodigoPostal, true);
			xml_receptor.AppendChild(xml_domicilioFiscalReceptor);

			//Concepto
			XmlElement xml_conceptos = xmlDoc.CreateElement("Conceptos");
			xml_comprobante.AppendChild(xml_conceptos);

			//Todos los conceptos
			foreach (Concepto c in Conceptos)
			{
				XmlElement xml_concepto = xmlDoc.CreateElement("Concepto");
				
				SetXML(xml_concepto, "cantidad", c.Cantidad);
				SetXML(xml_concepto, "unidad", c.Unidad, true);
				SetXML(xml_concepto, "noIdentificacion", c.NoIdentificacion, true);
				SetXML(xml_concepto, "descripcion", c.Descripcion);
				SetXML(xml_concepto, "valorUnitario", c.ValorUnitario);
				SetXML(xml_concepto, "importe", c.Importe);
				xml_conceptos.AppendChild(xml_concepto);
			}

			//Impuestos
			XmlElement xml_impuestos = xmlDoc.CreateElement("Impuestos");
			xml_comprobante.AppendChild(xml_impuestos);

			//Retenciones
			if (Retenciones.Count > 0)
			{
				XmlElement xml_retenciones = xmlDoc.CreateElement("Retenciones");
				xml_impuestos.AppendChild(xml_retenciones);

				//Todas las retenciones
				foreach (Retencion r in Retenciones)
				{
					XmlElement xml_retencion = xmlDoc.CreateElement("Retencion");
					SetXML(xml_retencion, "impuesto", r.Impuesto);
					SetXML(xml_retencion, "importe", r.Importe);
					xml_retenciones.AppendChild(xml_retencion);
				}
			}

			//Traslados
			if (Traslados.Count > 0)
			{
				XmlElement xml_traslados = xmlDoc.CreateElement("Traslados");
				xml_impuestos.AppendChild(xml_traslados);

				//Todos los traslados
				foreach (Traslado t in Traslados)
				{
					XmlElement xml_traslado = xmlDoc.CreateElement("Traslado");
					SetXML(xml_traslado, "impuesto", t.Impuesto);
					SetXML(xml_traslado, "tasa", t.Tasa);
					SetXML(xml_traslado, "importe", t.Importe);
					xml_traslados.AppendChild(xml_traslado);
				}
			}

			return xmlDoc;
		}

		/// <summary>
		/// Formatea un valor decimal para ser almacenado en el XML, deacuerdo al estándar del SAT
		/// </summary>
		/// <param name="valor">Número decimal que será escrito en el XML</param>
		/// <returns>Representación en cadena de texto del valor decimal</returns>
		protected string ParseXML(decimal valor)
		{
			return valor.ToString("F2");
		}

		/// <summary>
		/// Formatea una enumeración para ser almacenada en el XML, deacuerdo al estándar del SAT
		/// </summary>
		/// <param name="valor">Enumeración que será escrito en el XML</param>
		/// <returns>Representación en cadena de texto de la enumeración</returns>
		protected string ParseXML(Enum valor)
		{
			return valor.ToString();
		}

		/// <summary>
		/// Formatea un valor de tipo texto para ser almacenado en el XML, deacuerdo al estándar del SAT
		/// </summary>
		/// <param name="valor">Cadena de texto que será escrita en el XML</param>
		/// <returns>Representación de la cadena de texto sin espacios en los extremos, ni caracteres no permitidos</returns>
		protected string ParseXML(string valor)
		{
			if (valor == null) return null;
			
			valor = valor.Trim();
			if (valor.Trim() == "") return null;

			valor = valor.Replace("\n", "");
			valor = valor.Replace("\t", "");
			valor = valor.Replace("\r", "");

			while (valor.Contains("  "))
			{
				valor = valor.Replace("  ", " ");
			}

			return valor;
		}
		/// <summary>
		/// Formatea un valor de tipo fecha para ser almacenado en el XML, deacuerdo al estándar del SAT
		/// </summary>
		/// <param name="valor">Fecha-hora que será escrita en el XML</param>
		/// <returns>Representación en cadena de texto de la fecha-hora</returns>
		protected string ParseXML(DateTime value)
		{
			return value.ToString("yyyy-MM-ddTHH:mm:ss");
		}

		/// <summary>
		/// Establece el valor de un atributo a un elemento de XML
		/// </summary>
		/// <param name="elemento">El elemento de XML al cual se le establecerá el valor de un atributo</param>
		/// <param name="nombre">Nombre del atributo</param>
		/// <param name="valor">Valor del atributo</param>
		protected void SetXML(XmlElement elemento, string nombre, object valor)
		{
			SetXML(elemento, nombre, valor, false);
		}

		/// <summary>
		/// Establece el valor de un atributo a un elemento de XML
		/// </summary>
		/// <param name="elemento">El elemento de XML al cual se le establecerá el valor de un atributo</param>
		/// <param name="nombre">Nombre del atributo</param>
		/// <param name="valor">Valor del atributo</param>
		/// <param name="opcional">
		///		Define si el atributo es opcional o no
		///		<remarks>Si es opcional y el valor es NULL o vacío, el atributo no se escribe. Si no es opcional y el valor es NULL o vacío, se manda un error</remarks>
		/// </param>
		protected void SetXML(XmlElement elemento, string nombre, object valor, bool opcional)
		{
			string _valor = null;

			if (valor == null && !opcional) throw new ArgumentNullException(nombre, "La propiedad '" + nombre + "' del elemento " + elemento.Name + " no puede ser NULL");

			if (valor is string) _valor = ParseXML((string)valor);
			else if (valor is decimal) _valor = ParseXML((decimal)valor);
			else if (valor is Enum) _valor = ParseXML((Enum)valor);
			else if (valor is DateTime) _valor = ParseXML((DateTime)valor);
			else if (valor != null) _valor = valor.ToString();

			//Si despues del Parse, es NULL, marcar error
			if (_valor == null && !opcional) throw new ArgumentNullException(nombre, "La propiedad '" + nombre + "' del elemento " + elemento.Name + " no puede ser NULL");

			//Si es nulo y es opcional, no hacer nada
			if (_valor == null && opcional) return;

			elemento.SetAttribute(nombre, _valor);
		}

		#endregion

		/// <summary>
		/// Devuelve el número de factura incluyendo la serie
		/// </summary>
		/// <example>A100</example>
		public override string ToString()
		{
			return NoFactura;
		}

		/// <summary>
		/// Crea la cadena original de la factura y la guarda en la carpeta temporal
		/// </summary>
		protected void CrearCadenaOriginal()
		{
			XPathDocument pathDoc = new XPathDocument(CarpetaTemporal + NoFactura + "_sin_sello.xml");
			XslCompiledTransform xslTrans = new XslCompiledTransform();
			XmlTextWriter writer = new XmlTextWriter(CarpetaTemporal + NoFactura + "_cadena_original_temp.txt", Encoding.UTF8);

			//Guardar como texto
			xslTrans.Load(CarpetaRecursos + "CadenaOriginal.xslt");
			xslTrans.Transform(pathDoc, null, writer);
			writer.Close();

			//Abrir, convertir a UTF8 y volver a guardar como bytes
			string cadena = File.ReadAllText(CarpetaTemporal + ToString() + "_cadena_original_temp.txt");
			File.WriteAllBytes(CarpetaTemporal + NoFactura + "_cadena_original.txt", Encoding.UTF8.GetBytes(cadena));
		}

		/// <summary>
		/// Crea el sello de la factura y lo guarda en la carpeta temporal
		/// </summary>
		protected void CrearSello()
		{
			ProcessStartInfo processStart;
			System.Diagnostics.Process process;
			string prefijoTemporal;

			prefijoTemporal = CarpetaTemporal + NoFactura;

			//Crear cadena original, convertirla a UTF-8 y guardarla
			CrearCadenaOriginal();

			//Generar archivo MD5
			processStart = new ProcessStartInfo();
			processStart.WindowStyle = ProcessWindowStyle.Hidden;

			processStart.FileName = CarpetaRecursos + "openssl.exe";
			processStart.Arguments =
				"dgst -md5 -sign " +
				"\"" +
				CarpetaRecursos +
				"LlavePrivada.key.pem" +
				"\"" +
				" -out " +
				"\"" +
				prefijoTemporal + "_md5.txt" +
				"\" " +
				"\"" +
				prefijoTemporal + "_cadena_original.txt" +
				"\"";

			process = new System.Diagnostics.Process();
			process.StartInfo = processStart;
			process.EnableRaisingEvents = true;
			process.Start();

			while (!process.HasExited)
			{
				System.Threading.Thread.Sleep(1000);
			}

			//check to see what the exit code was
			if (process.ExitCode != 0)
			{
				throw new Exception("Error al generar el sello");
			}

			//Generar sello
			processStart.Arguments =
				"enc -base64 -in " +
				"\"" +
				prefijoTemporal + "_md5.txt" +
				"\"" +
				" -out " +
				"\"" +
				prefijoTemporal + "_sello.txt" +
				"\"";


			process = new System.Diagnostics.Process();
			process.StartInfo = processStart;
			process.EnableRaisingEvents = true;
			process.Start();

			while (!process.HasExited)
			{
				System.Threading.Thread.Sleep(1000);
			}

			//check to see what the exit code was
			if (process.ExitCode != 0)
			{
				throw new Exception("Error al generar el sello");
			}
		}

		/// <summary>
		/// Genera un archivo XML con la factura electrónica
		/// </summary>
		/// <returns>La ruta donde fué generada la factura en formato XML</returns>
		public string GuardarXml()
		{
			XmlDocument xml;

			//Primero crear XML sin el sello
			xml = CrearXML();
			xml.Save(CarpetaTemporal + NoFactura + "_sin_sello.xml");

			//Crear sello
			CrearSello();
			this.Sello = File.ReadAllText(CarpetaTemporal + NoFactura + "_sello.txt");

			//Crear y guardar XML sellado
			xml = CrearXML();
			xml.Save(CarpetaFacturas + NoFactura + ".xml");

			rutaXML = CarpetaFacturas + NoFactura + ".xml";
			return rutaXML;
		}

		/// <summary>
		/// Genera un archivo imprimible HTML
		/// </summary>
		/// <returns>La ruta donde fué generada la factura en formato HTML</returns>
		public string GuardarHtml()
		{
			XPathDocument pathDoc;
			XslCompiledTransform xslTrans;
			XmlTextWriter writer;
			string html;
			string cadenaOriginal;

			pathDoc = new XPathDocument(CarpetaFacturas + NoFactura + ".xml");
			xslTrans = new XslCompiledTransform();
			writer = new XmlTextWriter(CarpetaFacturas + NoFactura + ".html", Encoding.Unicode);

			//Guardar como texto
			xslTrans.Load(CarpetaRecursos + "ConvertirHTML.xslt");
			xslTrans.Transform(pathDoc, null, writer);
			writer.Close();

			rutaHTML = CarpetaFacturas + NoFactura + ".html";
			
			//Poner la cadena original
			html = File.ReadAllText(rutaHTML);
			cadenaOriginal = File.ReadAllText(CarpetaTemporal + NoFactura + "_cadena_original.txt", Encoding.UTF8);
			html = html.Replace("<CadenaOriginal />", cadenaOriginal);
			File.WriteAllText(rutaHTML, html, Encoding.Unicode);

			return rutaHTML;
		}
	}
}